﻿using System;
using System.Text;
using System.Threading;

namespace FreeSwitch.EventSocket.Parser
{
	public class MessageParser
	{
		private readonly StringBuffer _buffer = new StringBuffer(65535);
		private readonly Encoding _encoding = Encoding.ASCII;
		private readonly ManualResetEvent _event = new ManualResetEvent(false);
		private readonly ILogger _logger = LogManager.GetLogger(typeof (MessageParser));
		private readonly Thread _parserThread;
		private ParsedMessage _msg = new ParsedMessage();
		private ParserMethod _parserMethod;

		/// <summary>
		/// Initializes a new instance of the <see cref="MessageParser"/> class.
		/// </summary>
		public MessageParser()
		{
			_parserThread = new Thread(OnParseMessages);
			_parserMethod = ParseBeforeHeader;
		}

		public void Start()
		{
			_parserThread.Start();
		}

		public void Append(byte[] buffer, int offset, int count)
		{
			lock (_buffer)
				_buffer.Append(_encoding.GetChars(buffer, offset, count));

			_event.Set();
		}

		public void Reset()
		{
			lock (_buffer)
				_buffer.Clear();

			_parserMethod = ParseBeforeHeader;
			_msg = new ParsedMessage();
		}

		private void OnParseMessages()
		{
			while (true)
			{
				if (!_event.WaitOne(Timeout.Infinite))
				{
					_logger.Warning("Exiting parsing loop");
					break;
				}
				_event.Reset();

				try
				{
					ParseMessages();
				}
				catch (Exception err)
				{
					ExceptionThrown(this, new ExceptionEventArgs(err));
				}
			}
		}

		private bool ParseHeader()
		{
			string line = ReadLine();
			while (line != null)
			{
				if (line == string.Empty)
				{
					_parserMethod = ParseBody;
					return true;
				}

				int pos = line.IndexOf(':');
				if (pos == -1)
					throw new FormatException("Failed to find colon in a header. Line: " + line);

				string name = line.Substring(0, pos).Trim();
				string value = line.Substring(pos + 1).Trim();
				_msg.Headers.Add(name, value);
				_logger.Trace("Header [" + name + ": " + value + "]");

				line = ReadLine();
			}

			return false;
		}

		private bool ParseBody()
		{
			string temp = _msg.Headers["Content-Length"];
			if (temp == null)
			{
				BodyCompleted();
				return false;
			}

			int contentLength;
			if (!int.TryParse(temp, out contentLength))
			{
				_logger.Error("Incorrect content-length: " + temp);
				throw new FormatException("Could not parse Content-Length value: " + temp);
			}
			if (contentLength == 0)
			{
				BodyCompleted();
				return false;
			}

			int bytesRemaining = contentLength - _msg.Body.Length;
			if (bytesRemaining >= _buffer.Count)
			{
				_logger.Trace("Need " + bytesRemaining + " more bytes");
				return false;
			}

			var buffer = new char[bytesRemaining];
			lock (_buffer)
			{
				_buffer.Pop(buffer, 0, bytesRemaining);
			}

			_msg.Body.Append(buffer, 0, bytesRemaining);
			BodyCompleted();
			/*if (read==bytesRemaining)
			{
				_logger.Debug("Body is now complete.");
				return false;
			}

			// need more bytes.
			_logger.Debug("Need " + bytesRemaining + " more bytes");
			 * */
			return false;
		}

		private void BodyCompleted()
		{
			_parserMethod = ParseBeforeHeader;
			_logger.Debug("Message parsed successfully: " + _msg);
			ParsedMessage tmp = _msg;
			ThreadPool.QueueUserWorkItem(o => MessageReceived(this, new MessageEventArgs(tmp)));
			_msg = new ParsedMessage();
		}

		private bool ParseBeforeHeader()
		{
			_logger.Trace("Reading empty lines before header.");
			while (_buffer.Peek() == '\n')
				_buffer.ReadLine();

			_parserMethod = ParseHeader;
			return true;
		}

		private void ParseMessages()
		{
			while (_parserMethod())
			{
				_logger.Trace(_parserMethod.Method.Name + "...");
			}
		}

		private string ReadLine()
		{
			lock (_buffer)
			{
				string tmp = _buffer.ReadLine();
				return tmp;
			}
		}

		public event EventHandler<ExceptionEventArgs> ExceptionThrown = delegate { };
		public event EventHandler<MessageEventArgs> MessageReceived = delegate { };

		#region Nested type: ParserMethod

		private delegate bool ParserMethod();

		#endregion
	}

	public class ExceptionEventArgs : EventArgs
	{
		public ExceptionEventArgs(Exception exception)
		{
			Exception = exception;
		}

		public Exception Exception { get; set; }
	}

	public class MessageEventArgs : EventArgs
	{
		public MessageEventArgs(ParsedMessage msg)
		{
			Message = msg;
		}

		public ParsedMessage Message { get; private set; }
	}
}